package server;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

import server.core.dao.DaoFactory.daoType;
import server.core.util.Authentication;

import communication.Message;
import communication.exception.AuthenticationException;

public class Network implements Runnable {
	private String[] args;
	private PrintWriter printWriter;
	public Network(String[] args, OutputStream outputStream){
		this.args=args;
		this.printWriter=new PrintWriter(outputStream);
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		go(args);
	}
	
	public static final int PORT_NUMBER = 12345;
	
	public void go(String[] args) {
		try{
			int port = PORT_NUMBER;
			if (args.length > 0)
			{ // Override default listen port
				port = Integer.parseInt(args[0]);
			}
			System.out.println("Listening on port "+port);
			ServerSocketChannel serverChannel = ServerSocketChannel.open();
			ServerSocket serverSocket = serverChannel.socket();
			Selector selector = Selector.open();
			serverSocket.bind(new InetSocketAddress(port));
			serverChannel.configureBlocking(false);
			serverChannel.register(selector, SelectionKey.OP_ACCEPT);
			while (true) {
				int n = selector.select();
				if (n == 0) {
					continue;
				}
				Iterator it = selector.selectedKeys().iterator();
				while (it.hasNext()) {
					SelectionKey key = (SelectionKey) it.next();
					if (key.isAcceptable()) {
						ServerSocketChannel server = (ServerSocketChannel) key.channel();
						SocketChannel channel = server.accept();
						registerChannel(selector, channel, SelectionKey.OP_READ);
					}
					if (key.isReadable()) {
						readDataFromSocket(key);
					}
					it.remove();
				}
			}
		} catch (Exception exception) {
			exception.printStackTrace();
		}
	}
	
    /**
     * Register the given channel with the given selector for the given operations of interest
     * 
     * @param selector
     * @param channel
     * @param ops
     * @throws IOException 
     */
	protected void registerChannel(Selector selector, SelectableChannel channel, int ops) throws IOException{
        if (channel == null) {
        	return; // could happen
        }
        channel.configureBlocking(false);
        channel.register(selector, ops);
    }
	
	// Use the same byte buffer for all channels. A single thread is
	// servicing all the channels, so no danger of concurrent access.
	private ByteBuffer buffer = ByteBuffer.allocateDirect(65536);

    /**
     * Sample data handler method for a channel with data ready to read.
     *
     * @param key A SelectionKey object associated with a channel
     *            determined by the selector to be ready for reading. If the
     *            channel returns an EOF condition, it is closed here, which
     *            automatically invalidates the associated key. The selector
     *            will then de-register the channel on the next select call.
     * @throws AuthenticationException 
     * @throws IOException 
     */
	protected void readDataFromSocket(SelectionKey key) throws AuthenticationException, IOException {
			SocketChannel socketChannel = (SocketChannel) key.channel();
			int count = -1;
			buffer.clear();
			try {
				while ((count = socketChannel.read(buffer)) > 0){
					buffer.flip(); // Make buffer readable
					// Send the data; don't assume it goes all at once
					while (buffer.hasRemaining()){
						
						CLI cli=new CLI(daoType.MEMORY, new String[]{""});
						cli.setCurrentUser(Authentication.getInstance().signup("Jack", "jACK"));

//                WritableByteChannel stdOutChannel = Channels.newChannel(System.out);
//                stdOutChannel.write(buffer);
						byte[] bs=new byte[buffer.limit()];
						buffer.get(bs);
						Message request=Message.objFromByte(bs);
						if(request!=null)
						System.out.println(socketChannel.socket().getInetAddress().toString()+request.getCommand());
						Message response=cli.checkStr(request);
						writeDataToSocket(response, socketChannel);
					}
// WARNING: the above loop is evil. Because
// it's writing back to the same nonblocking
// channel it read the data from, this code can
// potentially spin in a busy loop. In real life
// you'd do something more useful than this.
					buffer.clear(); // Empty buffer
				}
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if (count < 0){
				// Close channel on EOF, invalidates the key
				socketChannel.close();
			}
	}

	/**
	 * @param message
	 * @param channel
	 * @throws IOException
	 */
	private void writeDataToSocket(Message message, SocketChannel channel) throws IOException {
		buffer.clear();
		buffer.put(Message.byteFromObj(message));
		buffer.flip();
		channel.write(buffer);
	}

}